﻿using System.Linq;

using Microsoft.WowAddonStudio.Lua.AST;
using Microsoft.WowAddonStudio.Lua.Model;
using Microsoft.WowAddonStudio.Lua.Parser;

using NUnit.Framework;

using Function = Microsoft.WowAddonStudio.Lua.AST.Function;

namespace Microsoft.WowAddonStudio.Lua.Tests.Parser
{
    [TestFixture]
    public class ScopedParserTest : ParserTestBase<ScopedParser<Scope>>
    {
        private readonly Scope globalScope = new Scope();

        public override void SetUp()
        {
            // Create the parser
            parser = new ScopedParser<Scope>(globalScope)
                     {
                         Tracing = true,
                     };
        }

        [Test]
        public void AssignmentTest()
        {
            // Set the source to a single statement that declares variable 'x'
            parser.SetSource("x = 5");

            // Assert the parse
            this.AssertParse();

            Assert.IsNotNull(parser.Chunk.Block);
            Assert.IsNotNull(parser.Chunk.Block.StatementList);

            // Assert assignment statement
            var assignmentStatement = parser.Chunk.Block.StatementList as Assignment;

            Assert.IsNotNull(assignmentStatement);
            Assert.IsNotNull(assignmentStatement.VariableList);
            Assert.IsNotNull(assignmentStatement.ExpressionList);

            Assert.AreSame(parser.Chunk.Block.Scope, assignmentStatement.Scope);
        }

        [Test]
        public void ExplicitScopeTest()
        {
            // Set the source to a single statement in an explicit scope that declares variable 'x'
            parser.SetSource("do x = 5 end");

            // Assert the parse
            this.AssertParse();

            Assert.IsNotNull(parser.Chunk.Block);
            Assert.IsNotNull(parser.Chunk.Block.StatementList);

            // Assert explicit block and inner block
            var explicitBlock = parser.Chunk.Block.StatementList as ExplicitBlock;

            Assert.IsNotNull(explicitBlock);
            Assert.IsNotNull(explicitBlock.Block);

            // Assert assignment statement
            var assignmentStatement = explicitBlock.Block.StatementList as Assignment;

            Assert.IsNotNull(assignmentStatement);
            Assert.IsNotNull(assignmentStatement.VariableList);
            Assert.IsNotNull(assignmentStatement.ExpressionList);

            // Assert that the assignment's scope is not the parser scope but its child
            Assert.AreNotSame(parser.Chunk.Block.Scope, assignmentStatement.Scope);
            Assert.AreSame(parser.Chunk.Block.Scope, assignmentStatement.Scope.Parent);
        }

        [Test]
        public void FunctionScopeTest()
        {
            // Set the source to a function that has a local declaration in it
            parser.SetSource("function foo() local x = 5 end");

            // Assert the parse
            this.AssertParse();

            Assert.IsNotNull(parser.Chunk.Block);
            Assert.IsNotNull(parser.Chunk.Block.StatementList);

            // Assert explicit block and inner block
            var functionDeclaration = parser.Chunk.Block.StatementList as FunctionDeclaration;

            Assert.IsNotNull(functionDeclaration);
            Assert.IsNotNull(functionDeclaration.Body);

            // Assert assignment statement
            var assignmentStatement = functionDeclaration.Body.StatementList as Assignment;

            Assert.IsNotNull(assignmentStatement);
            Assert.IsNotNull(assignmentStatement.VariableList);
            Assert.IsNotNull(assignmentStatement.ExpressionList);

            // Assert that the assignment's scope is not the parser scope but its child
            Assert.AreNotSame(parser.Chunk.Block.Scope, assignmentStatement.Scope);
            Assert.AreSame(parser.Chunk.Block.Scope, assignmentStatement.Scope.Parent);
        }

        [Test]
        public void LocalFunctionScopeTest()
        {
            // Set the source to a function that has a local declaration in it
            parser.SetSource("local function foo() local x = 5 end");

            // Assert the parse
            this.AssertParse();

            Assert.IsNotNull(parser.Chunk.Block);

            // Assert explicit block and inner block
            var functionDeclaration = parser.Chunk.Block.StatementList as FunctionDeclaration;

            Assert.IsNotNull(functionDeclaration);
            Assert.IsNotNull(functionDeclaration.Body);

            // Assert assignment statement
            var assignmentStatement = functionDeclaration.Body.StatementList as Assignment;

            Assert.IsNotNull(assignmentStatement);
            Assert.IsNotNull(assignmentStatement.VariableList);
            Assert.IsNotNull(assignmentStatement.ExpressionList);

            // Assert that the assignment's scope is not the parser scope but its child
            Assert.AreNotSame(parser.Chunk.Block.Scope, assignmentStatement.Scope);
            Assert.AreSame(parser.Chunk.Block.Scope, assignmentStatement.Scope.Parent);
        }

        [Test]
        public void FunctionExpressionScopeTest()
        {
            // Set the source to a function that has a local declaration in it
            parser.SetSource("x = function() local x = 5 end");

            // Assert the parse
            this.AssertParse();

            Assert.IsNotNull(parser.Chunk.Block);
            Assert.IsNotNull(parser.Chunk.Block.StatementList);

            // Assert explicit block and inner block
            var assignment = parser.Chunk.Block.StatementList as Assignment;

            Assert.IsNotNull(assignment);
            Assert.IsNotNull(assignment.VariableList);
            Assert.IsNotNull(assignment.ExpressionList);

            // Assert function
            var function = assignment.ExpressionList as Function;

            Assert.IsNotNull(function);
            Assert.IsNotNull(function.Body);

            // Assert assignment statement
            var innerAssignment = function.Body.StatementList as Assignment;

            Assert.IsNotNull(innerAssignment);
            Assert.IsNotNull(innerAssignment.VariableList);
            Assert.IsNotNull(innerAssignment.ExpressionList);

            // Assert that the assignment's scope is not the parser scope but its child
            Assert.AreNotSame(parser.Chunk.Block.Scope, innerAssignment.Scope);
            Assert.AreSame(parser.Chunk.Block.Scope, innerAssignment.Scope.Parent);
        }

        [Test]
        public void ScopeLocationTest()
        {
            var source = "print('hello from chunk!')";

            // Set the source to a simple statement
            parser.SetSource(source);

            // Assert the parser
            this.AssertParse();

            // Assert the location of the scopes
            Assert.AreEqual(null, globalScope.Location);
            Assert.AreEqual(new LexLocation(0, 0, 0, source.Length), parser.Scope.Location);
        }

        [Test]
        public void ExplicitScopeLocationTest()
        {
            // Set the source to an explicit scope
            parser.SetSource(@"do
print('hello from scope!')
end");

            // Assert the parser
            this.AssertParse();

            // Assert the location of the function declaration scope
            Assert.AreEqual(new LexLocation(0, 0, 2, 3), parser.Scope.ChildScopes.First().Location);
        }

        [Test]
        public void FunctionDeclarationScopeLocationTest()
        {
            // Set the source to an explicit scope
            parser.SetSource(@"function mul(x, y)
return x * y
end

print(mul(5, 5);");

            // Assert the parser
            this.AssertParse();

            Assert.AreEqual(new LexLocation(0, 12, 2, 3), parser.Scope.ChildScopes.First().Location);
        }

        [Test]
        public void FindScopeTest()
        {
            // Set the source to an explicit scope
            parser.SetSource(@"function mul(x, y)
return x * y
end

print(mul(5, 5);");

            // Assert the parser
            this.AssertParse();

            // Assert that FindScope returns the correct scopes for different positions
            Assert.AreSame(parser.Scope, parser.FindScope(4, 1));
            Assert.AreSame(parser.Scope.ChildScopes.First(), parser.FindScope(1, 1));
        }
    }
}